#pragma once
#include <Windows.h>

#include <Debug/log.hpp>
#include <cassert>
#include <cinttypes>
#include <exception>
#include <iostream>
#include <new>
#include <string>

namespace multiprocess {
namespace shm_allocator {
// meta+bitmap+index_table
// sharememory->meta->bitmap     ->shm_pool->allocator->mySTL
//                   index_table

using namespace demo::debug;
// useless
struct meta_t {
  static const size_t MAGIC_NUM = 0x34684238;

  size_t magic_num = MAGIC_NUM;
  size_t bitmap_size;
  size_t block_size;
  size_t max_index_num;
  size_t index_table_offset;

  bool try_load(meta_t const *meta_ptr) {
    if (meta_ptr->magic_num != MAGIC_NUM) return false;
    magic_num = meta_ptr->magic_num;
    bitmap_size = meta_ptr->bitmap_size;
    block_size = meta_ptr->block_size;
    max_index_num = meta_ptr->max_index_num;
    index_table_offset = meta_ptr->index_table_offset;
    return true;
  }
  void init(size_t bitmap_size, size_t block_size, size_t max_index_num) {}
};

// get shm
struct ShareMemory {
  ShareMemory(size_t shm_key, size_t size)
      : shm_key(shm_key), size(size), base_ptr(nullptr) {
    hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
                                 size, ::std::to_string(shm_key).data());
    assert(hMapFile);
    if (GetLastError() == ERROR_ALREADY_EXISTS) {
      log::debug("share memory {} has created.", shm_key);
    }
    base_ptr = static_cast<char *>(
        MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, size));
    assert(base_ptr);
  }
  ~ShareMemory() {
    UnmapViewOfFile(base_ptr);
    CloseHandle(hMapFile);
  }
  char *get_base() const { return base_ptr; }
  size_t get_size() const { return size; }
  size_t get_shmkey() const { return shm_key; }

 private:
  char *base_ptr;
  size_t size;
  size_t shm_key;
  HANDLE hMapFile;
};

// how to alloc shm, by block
class ShareBitmap {
 public:
  static const size_t MAGIC_NUM = 0x34684238;

  struct info_t {
    size_t magic_num = MAGIC_NUM;
    size_t bitmap_size;
    size_t block_size;
    size_t total_size;
  };
  ShareBitmap() = default;
  ShareBitmap(char *const shm_base_ptr, size_t bitmap_size,
              size_t block_size = 4 * 1024) {
    init(shm_base_ptr, bitmap_size, block_size);
  }
  void init(char *const shm_base_ptr, size_t bitmap_size,
            size_t block_size = 4) {
    m_info_ptr = (info_t *)(shm_base_ptr);
    m_bitmap_ptr = (uint32_t *)(shm_base_ptr + sizeof(info_t));
    if (!already_exist_bitmap(m_info_ptr)) {
      init_info(m_info_ptr, bitmap_size, block_size);
      m_info_ptr->total_size = init_data(m_bitmap_ptr, bitmap_size, block_size);
    }
    m_data_base_ptr = shm_base_ptr + m_info_ptr->total_size;
  }
  bool test(size_t pos) const {
    assert(pos < m_info_ptr->bitmap_size);
    size_t which_uint = pos >> 5;
    size_t offset = pos % 32;
    return m_bitmap_ptr[which_uint] & (1ul << offset);
  }
  void set(size_t pos) {
    size_t which_uint = pos >> 5;
    size_t offset = pos % 32;
    m_bitmap_ptr[which_uint] |= (1ul << offset);
  }
  void reset(size_t pos) {
    size_t which_uint = pos >> 5;
    size_t offset = pos % 32;
    m_bitmap_ptr[which_uint] &= ~(1ul << offset);
  }
  size_t alloc(size_t block_num) {
    size_t tmp_pos = find_n_free_bits(block_num);
    if (tmp_pos == m_info_ptr->bitmap_size) return tmp_pos;
    size_t ret = tmp_pos;
    while (block_num--) {
      set(tmp_pos++);
    }
    return ret;
  }
  void dealloc(size_t pos, size_t block_num) {
    while (block_num--) {
      reset(pos++);
    }
  }
  size_t block_size() const { return m_info_ptr->block_size; }
  size_t bitmap_size() const { return m_info_ptr->bitmap_size; }
  char *base_ptr() const { return (char *)(m_info_ptr); }
  char *data_ptr() const { return base_ptr() + m_info_ptr->total_size; }
  void debug_str() const {
    std::cout << "debug info";
    for (size_t i = 0; i < bitmap_size(); i++) {
      if (i % 50 == 0) std::cout << "\n\t";
      if (test(i))
        std::cout << '1';
      else
        std::cout << '0';
    }
    std::cout << "\n";
  }

 private:
  size_t how_many_block(size_t space_byte) const {
    size_t ret = space_byte / block_size();
    if (space_byte % block_size()) ++ret;
    return ret;
  }
  static size_t get_uint_num(size_t bitmap_size) {
    size_t ret = bitmap_size >> 5;
    if (bitmap_size % 32) ++ret;
    return ret;
  }
  bool already_exist_bitmap(info_t const *p) {
    return p->magic_num == MAGIC_NUM;
  }
  void init_info(info_t *p, size_t bitmap_size, size_t block_size) {
    p->bitmap_size = bitmap_size;
    p->block_size = block_size;
    p->magic_num = MAGIC_NUM;
  }
  size_t init_data(volatile uint32_t *p, size_t bitmap_size,
                   size_t block_size) {
    memset((uint32_t *)(p), 0, get_uint_num(bitmap_size) * sizeof(uint32_t));
    size_t total_size =
        get_uint_num(bitmap_size) * sizeof(uint32_t) + sizeof(info_t);
    size_t used_block_num = total_size / block_size;
    if (total_size % block_size) ++used_block_num;
    size_t pos = 0;
    while (used_block_num--) {
      set(pos++);
    }
    return total_size;
  }
  size_t find_n_free_bits(size_t n) {
    for (size_t i = 0; i < m_info_ptr->bitmap_size; i++) {
      if (test(i) == false) {
        size_t cnt_free_bit = 0;
        for (size_t j = i; j < m_info_ptr->bitmap_size && j < i + n; j++) {
          if (test(j) == false)
            ++cnt_free_bit;
          else
            break;
        }
        if (cnt_free_bit == n) return i;
        i = i + cnt_free_bit;
      }
    }
    return m_info_ptr->bitmap_size;
  }

  char *data_base_ptr() const { return m_data_base_ptr; }
  info_t *m_info_ptr;
  volatile uint32_t *m_bitmap_ptr;
  char *m_data_base_ptr;
};

// useless
class ShareIndexTable {
  static size_t const MAGIC_NUM = 0x17457819;

  struct info_t {
    size_t maigc_num = MAGIC_NUM;
    size_t index_num = 0;
  };

  struct index_t {
    size_t name_hash;
    size_t offset;
  };
  ShareIndexTable(ShareBitmap *bitmap_ptr, size_t max_indexs_num)
      : m_max_index_num(max_indexs_num) {
    info_ptr = (info_t *)(bitmap_ptr->data_ptr());
  }

  void create_index(size_t hash_value, size_t offset) {}

  size_t m_max_index_num;
  info_t *info_ptr;
};

// shm + shm_bitmap
class ShareMemoryPool {
 public:
  ShareMemoryPool(size_t shm_key, size_t block_num, size_t block_size = 4)
      : shm(shm_key, block_num * block_size),
        bitmap(shm.get_base(), block_num, block_size) {}
  void *alloc(size_t bytes) {
    size_t block_num = how_many_block(bytes);
    return alloc_block(block_num);
  }

  void *alloc_block(size_t block_num) {
    size_t pos = bitmap.alloc(block_num);
    if (pos == bitmap.bitmap_size()) return nullptr;
    return (void *)(pos_to_ptr(pos));
  }

  void dealloc(void *ptr, size_t bytes) {
    size_t block_num = how_many_block(bytes);
    dealloc_block(ptr, block_num);
  }

  void dealloc_block(void *ptr, size_t block_num) {
    bitmap.dealloc(ptr_to_pos((char *)ptr), block_num);
  }

  void *get(size_t offset) { return base() + offset; }
  char *base() const { return shm.get_base(); }
  void debug_str() { bitmap.debug_str(); }
  char *data() const { return bitmap.data_ptr(); }
  size_t max_size() const { return shm.get_size(); }

  size_t block_size() const { return bitmap.block_size(); }
  size_t belong_which_block(void *ptr) const { return ptr_to_pos((char *)ptr); }

 private:
  size_t how_many_block(size_t space_byte) const {
    size_t ret = space_byte / bitmap.block_size();
    if (space_byte % bitmap.block_size()) ++ret;
    return ret;
  }
  char *pos_to_ptr(size_t pos) const {
    return bitmap.base_ptr() + pos * bitmap.block_size();
  }
  size_t ptr_to_pos(char *p) const {
    int tmp = p - bitmap.base_ptr();
    assert(tmp >= 0);
    return tmp / bitmap.block_size();
  }
  ShareMemory shm;
  ShareBitmap bitmap;
};

struct NoMoreSpaceExpection : public std::exception {
  char const *what() const noexcept override {
    return "no more share memory to alloc.\n";
  }
};

template <typename T>
class ShareMemoryAllocator {
 public:
  using value_type = T;
  using pointer = T *;
  using const_pointer = T const *;
  using void_pointer = void *;
  using const_void_pointer = void const *;
  using size_type = size_t;
  using difference_type = size_t;
  using reference = T &;
  using const_reference = T const &;
  using shared_pointer = size_t;
  using const_shared_pointer = size_t const;

  ShareMemoryAllocator(ShareMemoryPool *shm_pool_ptr)
      : m_shm_pool_ptr(shm_pool_ptr),
        m_base_ptr(m_shm_pool_ptr->base()),
        m_block_size(m_shm_pool_ptr->block_size()),
        m_used_space_count(0) {
    if (block_size() / 2 > sizeof(T)) {
      log::warn("maybe happen share memory leak");
    }
  };
  ~ShareMemoryAllocator() {
    if (m_used_space_count) {
      log::warn("share memory leak, {} bytes.", m_used_space_count);
    }
  };

  pointer address(reference r) const { return &r; }
  const_pointer address(const_reference r) const { return &r; }
  shared_pointer shared_address(reference r) const { return (&r) - shm_base(); }
  const_shared_pointer shared_address(const_reference r) const {
    return (&r) - shm_base();
  }

  pointer allocate_space(size_type num_of_objs) {
    pointer ptr =
        static_cast<pointer>(m_shm_pool_ptr->alloc(num_of_objs * sizeof(T)));
    if (ptr == nullptr) throw NoMoreSpaceExpection();
    increase_objs_count(num_of_objs);
    return ptr;
  }

  pointer allocate(size_type num_of_objs) {
    pointer ptr = allocate_space(num_of_objs);
    construct(ptr, num_of_objs);
    return ptr;
  }
  pointer allocate(size_type num_of_objs, const_void_pointer hit) {
    return allocate(num_of_objs);
  }

  void deallocate_space(pointer ptr, size_type num_of_objs) {
    m_shm_pool_ptr->dealloc((void *)(ptr), num_of_objs * sizeof(value_type));
    decrease_objs_count(num_of_objs);
  }
  void deallocate(pointer ptr, size_type num_of_objs) {
    destroy(ptr, num_of_objs);
    deallocate_space(ptr, num_of_objs);
  }
  size_type max_size() const { return m_shm_pool_ptr->max_size(); }
  char const *shm_base() const { return m_base_ptr; }
  template <typename U>
  struct rebind {
    using other = ShareMemoryAllocator<U>;
  };

  void construct(pointer ptr, size_type num_of_objs = 1) {
    while (num_of_objs--) {
      new (ptr++) value_type();
    }
  }
  void destroy(pointer ptr, size_type num_of_objs = 1) {
    while (num_of_objs--) {
      ptr->~value_type();
    }
  }

 private:
  void increase_objs_count(size_t add) {
    m_used_space_count += add * sizeof(value_type);
  }
  void decrease_objs_count(size_t sub) {
    m_used_space_count -= sub * sizeof(value_type);
    assert(m_used_space_count >= 0);
  }
  size_t block_size() const { return m_block_size; }
  ShareMemoryPool *m_shm_pool_ptr;
  char const *const m_base_ptr;
  size_t m_block_size;
  int64_t m_used_space_count;
};
}  // namespace shm_allocator
}  // namespace multiprocess